package estadistec.logica.parser;

import estadistec.excepciones.Excepcion;
import estadistec.logica.tabla.LTabla;
import java.util.ArrayList;


/**
 * Clase para el parseo de expresiones.
 *
 * El objetivo de la clase DataObject es:
 *	- Abstraer todos los objetos de la gramática a este tipo: es decir, cada
 *	elemento de la gramática es parseado a este objeto.
 *	- Servir de estructura de datos para el árbol de parseo.
 *	- Siendo construido como árbol de parseo, devuelve el tipo final
 *	representado, es decir, el tipo del resultado dada la evaluación del
 *	árbol.
 *	- Convertir el árbol a expresiones listas para ser evaluadas,
 *  convirtiendo los parámetros de las funciones y las operaciones en
 *  arreglos de datos primitivos (int[], float[], string[], boolean[]) y
 *  convertir las operaciones en códigos (int[]) en el orden respectivo para
 *  ser evaluados (ver clase EstructuraEvaluable).
 * Cabe recordar que no es un árbol de búsqueda, por lo que el orden en el
 * árbol depende directamente de la expresión que se esté evaluando.
 */
public class DataObject implements Constantes
{
 	/**
 	 * Valor del Objecto representado por esta instancia.
 	 */
 	String valor = "";


	/**
	 * Para el "valor final" exclusivamente cuando son rangos.
	 */
	DataObjectRango valorRango = null;


	/**
	 * Tipo del dato representado por esta instancia.
	 * No es modificable
	 */
	protected DataObjectTipos tipo;


	/**
	 *  Para el arbol se usa tanto hijoDer como hijoIzq,
	 * para implementar una lista solo se usa hijoDer
	 * para ir al siguiente de la lista, en el caso que
	 * el objeto actual funcione como raiz, hijoIzq apunta
	 * al último de la lista.  Así, la lista está vacía
	 * si hijoIzq es null.
	 */
	protected DataObject hijoDer=null, hijoIzq=null;


	/**
	 * No hace falta que sea público pues el DiccionarioTipos
	 *  sigue el modelo de único objeto (patrón singleton)
	 * Es el diccionario de datos de las funciones soportadas en
	 *  el ambiente, es decir tiene la información de las
	 *  funciones soportadas por el lenguaje matemático de
	 *  definición de los atributos.
	 */
	static protected DiccionarioTipos diccionario = DiccionarioTipos.getInstance();


	/**
	 * Constructor de la clase
	 * @param val es el valor del objeto
	 * @param tipo es el DataObjectTipo (tipo) del dato
	 */
	public DataObject(String val, DataObjectTipos tipo)
	{
		valor = val;
		this.tipo = tipo;
	}


	/**
	 * Constructor específico para valores tipo Rango.
	 *
	 * @param abre paréntesis cuadrado de apertura
	 * @param cierra paréntesis cuadrado de cierre del rango
	 * @param valInicio valor inicial del rango
	 * @param valFinal valor final del rango
	 */
	public DataObject(String abre, DataObject valInicio, DataObject valFinal, String cierra)
	{
		valorRango = new DataObjectRango();
		valorRango.apertura = abre;
		valorRango.valorInicio = valInicio;
		valorRango.valorFin = valFinal;
		valorRango.cierre = cierra;
		tipo = DataObjectTipos.RANGO;
	}

	/**
	 * Imprime el valor del Objecto
	 */
	public String toString()
	{
		if(tipo!=DataObjectTipos.RANGO)
			return ETIQUETA_TIPO+tipo + "\t"+ETIQUETA_VALOR+ valor;
		return ETIQUETA_TIPO+tipo + " "+valorRango.toString();
	}

	/**
	 * Inserta en el árbol.  Primero a la izquierda y luego a la derecha.
	 * Si es una lista, no permite insertar, pues la lista utiliza tanto
	 *  a hijoIzq como a hijoDer, por lo que tira la excepción.
	 * No es inserción recursiva (este no es un árbol de búsqueda)
	 * @param obj Es el dato a insertar en este "nodo" del árbol
	 *
	 */
	public void insertar(DataObject obj)throws ParseException
	{
		if(hijoIzq == null)
		{
			hijoIzq = obj;
		}
		else if(hijoDer == null)
		{
			hijoDer = obj;
		}else
		{
			throw new ParseException(ERROR_3ER_ELEM_EN_ARBOL_BIN+valor
				+" "+ETIQUETA_VALOR+ obj.valor);
		}
	}


	/**
	 * Devuelve la EstructuraEvaluable, esta contiene el
	 *  tipo de retorno y la estructura necesaria para reliazar
	 *  la evaluación correspondiente.
	 * @param tabla  es la tabla a la que pertenece el atributo que va a contener esta estructura evaluable
	 * @param depende es la lista donde van a caer la lista de nodos de los cuales depende este atributo
	 * @return EstructuraEvaluable estructura semicompilada lista para ser ejecutada
	 */
	public EstructuraEvaluable getEstructuraEvaluable(LTabla tabla, ArrayList<IEvaluable> depende) throws Excepcion
	{
		EstructuraEvaluable resultado = new EstructuraEvaluable();
		EstructuraEvaluable primerParam= null, segundoParam= null;
		try
		{
			if(hijoIzq != null)
			{
				if(hijoDer != null)
				{
					segundoParam = this.hijoDer.getEstructuraEvaluable(tabla, depende);
					resultado.setSegundoParametro(segundoParam);
				}
				primerParam = this.hijoIzq.getEstructuraEvaluable(tabla, depende);
				resultado.setPrimerParametro(primerParam);
			}

			//seguido se debe procesar el nodo actual
			switch(this.tipo)
			{
				case IDENTIFICADOR:
					String identificador = (String) this.valor;
					IEvaluable t = (IEvaluable) tabla.getNodoXNombre(identificador);

					//se supone primero se hizo la comprobación del tipo  (getTipo))
					//así que esta excepción no debería ser lanzada
					if(t==null)
						throw new Excepcion("No se ha encontrado ningún elemento de nombre: "+this.valor.toString());
					if(!depende.contains(t)) depende.add(t);
					//si el identificador tiene instrucciones para su iteración, el resultado de ésta
					//debe ser entera.  Ej de iteración: ((i*3)+9)
					if( primerParam != null)
						if( primerParam.getTipo() != DataObjectTipos.ENTERO &&
							primerParam.getTipo() != DataObjectTipos.FLOTANTE )
						{
							throw new Excepcion("El resultado del uso de la expresión 'i' debe ser un número");
						}

					resultado.setValor(t);

					//estadistec.logica.tipos.TipoValor (enum)
					switch(t.getTipo())
					{
						case BOOLEANO:
							resultado.setTipo(DataObjectTipos.LISTA_BOOL);
							break;
						case ENTERO:
							resultado.setTipo(DataObjectTipos.LISTA_ENTERO);
							break;
						case FLOTANTE:
							resultado.setTipo(DataObjectTipos.LISTA_FLOTANTE);
							break;
						case INTERVALO:
							resultado.setTipo(DataObjectTipos.LISTA_RANGO);
							break;
						case CUALITATIVO:
							resultado.setTipo(DataObjectTipos.LISTA_CUALITATIVO);
							break;
					}
					/*si hay un identificador.(i+...*...)
					 *la estructura evaluable será la encargada de velar que hace con ella,
					 *el (i+...) queda guardado en el primer parámetro de la estructura   */
					break;
				case OPERADOR:
				case FUNCION:
					int idFuncion; //código para los operadores

					//paso 1: se busca el # de la operación (idFuncion).
					//los códigos de los operadores dependen de la cantidad de parámetros.
					//2 parámetros
					if(hijoDer != null)
					{
						idFuncion = diccionario.buscarFuncion(this.valor,
							primerParam.getTipo(), segundoParam.getTipo());
					}
					//1 parámetro
					else if(hijoIzq != null)
					{
						idFuncion = diccionario.buscarFuncion(this.valor, primerParam.getTipo());
					}
					//0 parámetros
					else
					{
						idFuncion = diccionario.buscarFuncion(this.valor);
					}
					resultado.setIdFuncion(idFuncion);

					//paso 2: con el # de la operación, se busca la info relacionada
					//        para el valor de retorno.
					DiccionarioTiposInfo infoFuncion = diccionario.buscarFuncion(idFuncion);
					if(infoFuncion != null)
					{
						resultado.setTipo(infoFuncion.getTipoRetorno());
					}
					else
					{
						//esto no puede ocurrir pues ya se tuvieron que haber revisado los tipos
						//con getTipo)
						System.out.println(ERROR_FATAL_DATAOBJECT + idFuncion);
					}
					break;
				//case BOOL:
					//no debe existir, porque no está en la construcción de la estructura (ni en la sintaxis)
				case ENTERO:
					resultado.setValor(new Integer(this.valor));
					resultado.setTipo(this.tipo);
					break;
				case FLOTANTE:
					resultado.setValor(new Float(this.valor));
					resultado.setTipo(this.tipo);
					break;
				case CUALITATIVO:
					resultado.setValor(this.valor);
					resultado.setTipo(this.tipo);
					break;
				case RANGO:
					resultado.setValor(this.valorRango.getIntervalo());
					resultado.setTipo(this.tipo);
					break;
			}
		}
		catch(Excepcion e)
		{
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
		return resultado;
	}



	/**
	 * Devuelve el tipo de este objeto.  Si es operación o función
	 *  busca la especificación de la operación según sus tipos de parámetros
	 *  y devuelve el tipo de retorno de dicha operación, así lo hace
	 *  también de manera recursiva.
	 */
	public DataObjectTipos getTipo(LTabla tabla) throws Excepcion
	{
		DataObjectTipos respuesta = null;
		int idFuncion;
		switch(this.tipo)
		{
			case IDENTIFICADOR:
				//se le pide el tipo de objeto al atributo dado por el identificador
				String identificador = (String) this.valor;
				IEvaluable t = (IEvaluable) tabla.getNodoXNombre(identificador);

				if(t==null)
					throw new Excepcion("No se ha encontrado ningún elemento de nombre: "+this.valor.toString());

				//estadistec.logica.tipos.TipoValor (enum)
				switch(t.getTipo())
				{
					case BOOLEANO:
						respuesta = DataObjectTipos.LISTA_BOOL;
						break;
					case ENTERO:
						respuesta = DataObjectTipos.LISTA_ENTERO;
						break;
					case FLOTANTE:
						respuesta = DataObjectTipos.LISTA_FLOTANTE;
						break;
					case INTERVALO:
						respuesta = DataObjectTipos.LISTA_RANGO;
						break;
					case CUALITATIVO:
						respuesta = DataObjectTipos.LISTA_CUALITATIVO;
						break;
				}
				break;
			case OPERADOR:
			case FUNCION:
				//paso 1: se busca el # de la operación.
				if(hijoIzq != null)
				{
					//un parámetro
					if(hijoDer == null)
					{
						idFuncion = diccionario.buscarFuncion(this.valor, hijoIzq.getTipo(tabla));
					}
					//dos parámetros
					else
					{
						idFuncion = diccionario.buscarFuncion(this.valor,
							hijoIzq.getTipo(tabla), hijoDer.getTipo(tabla));
					}
				}
				else //sin parámetros
				{
					idFuncion = diccionario.buscarFuncion(this.valor);
				}

				//paso 2: con el # de la operación, se busca la info relacionada
				//        para comprobar el valor de retorno.
				DiccionarioTiposInfo infoFuncion =
						diccionario.buscarFuncion(idFuncion);
				if(infoFuncion != null)
				{
					respuesta = infoFuncion.getTipoRetorno();
				}
				else
				{
					//TODO tirar excepción porq esto no puede ocurrir
					System.out.println(ERROR_FATAL_DATAOBJECT + idFuncion);
				}
				break;
			//case BOOL no debe existir...
			case ENTERO:
			case FLOTANTE:
			case CUALITATIVO:
			case RANGO:
				respuesta = this.tipo;
				break;
		}
		return respuesta;
	}

	/**
	 * Verifica si un DataObject corresponde a un operador o a una función, por su tipo.
	 * @param dObj DataObject a ser consultado
	 * @return True si dObj es operador o función
	 */
	protected boolean esOp(DataObject dObj)
	{
		return (dObj!=null &&
			(dObj.tipo==DataObjectTipos.OPERADOR || dObj.tipo==DataObjectTipos.FUNCION));
	}

	/**
	 * Imprime el contenido del árbol a la salida estándar.
	 */
	public void print()
	{
		System.out.println("Imprimiedo estructura de Arbol");
		this.print("");
	}

	/**
	 * Auxliar de impresión recursiva.
	 * @param tabs representa el nivel del objeto en el árbol
	 */
	protected void print(String tabs)
	{
		System.out.println(tabs+this.toString());
		if(hijoIzq != null)
		{
			hijoIzq.print(tabs+"\t");

			if(hijoDer != null) hijoDer.print(tabs+"\t");
			else System.out.println("\t"+tabs+"---");
		}
	}
}
